
#include <SDL/SDL.h>
#include <SDL/SDL_image.h>
#include <cmath>

#include "img.h"
#include "screen.h"
#include "errors.h"

#include <vector>
using namespace std;

static bool i_img = FALSE;
static vector<Texture*> allocation;

int init_img() {
	int flags;
	if (!((flags = IMG_Init(~0)) & (IMG_INIT_PNG | IMG_INIT_JPG))) {
		ELOG("SDL_img initialisation error: %s", IMG_GetError());
		return ESDL_INIT_IMG;
	}
	i_img = TRUE;
	return 0;
}

void kill_img() {
	vector<Texture*>::iterator it;
	for (it = allocation.begin(); it != allocation.end(); it ++) {
		if (*it) {
			delete *it;
		}
	}
	if (i_img) {
		IMG_Quit();
	}
}

/* class Texture */
void Texture::_init() {
	has_tex = FALSE;
	tex = 0;
	alloc_ref = allocation.size();
	allocation.push_back(this);
}

Texture::Texture () {
	_init();
}

Texture::Texture (const char* fname) {
	_init();
	fromFile(fname);
}

Texture::Texture (SDL_Surface* surface) {
	_init();
	fromSDLSurface(surface);
}

Texture::~Texture() {
	if (has_tex) {
		glDeleteTextures(1, &tex);
		GL_CHECK_ERROR();
		allocation[alloc_ref] = NULL;
		has_tex = FALSE;
	}
}

bool Texture::fromFile(const char* filename) {
	SDL_Surface* surface;
	if (!(surface = IMG_Load(filename))) {
		ELOG("Error loading texture %s: %s", filename, IMG_GetError());
		return FALSE;
	}
	if ((surface = fromSDLSurface(surface))) {
		SDL_FreeSurface(surface);
		return TRUE;
	} else {
		ELOG("Error converting texture %s", filename);
		GL_CHECK_ERROR();
		return FALSE;
	}
}

SDL_Surface* Texture::fromSDLSurface(SDL_Surface* surface) {
	/* Code derived from http://gpwiki.org/index.php/SDL:Tutorials:Using_SDL_with_OpenGL */
	GLint nOfColors;
	static GLuint max_tex_size = 0;
	GLenum texture_format = GL_RGBA;
	Uint32 new_w, new_h;
	Uint32 shift_w = 0, shift_h = 0;
	float aspect;
	
	if (!surface) {
		return NULL;
	}
	tmaxW = tmaxH = 0;

	if (!max_tex_size) {
		glGetIntegerv(GL_MAX_TEXTURE_SIZE, (GLint*)&max_tex_size);
	}
	// For testing purposes
	max_tex_size = 512;
	
	texW = new_w = surface->w;
	texH = new_h = surface->h;
	// Check that the image's dimensions are powers of 2
	if ( (new_w & (new_w - 1)) ) {
		for (shift_w = 0; (new_w = new_w >> 1); shift_w ++);
		new_w = 1 << ++shift_w;
	} else {
		tmaxW = 1.0;
	}
	if ((new_h & (new_h - 1))) {
		for (shift_h = 0; (new_h = new_h >> 1); shift_h ++);
		new_h = 1 << ++shift_h;
	} else {
		tmaxH = 1.0;
	}

	new_w = min(new_w, (unsigned)max_tex_size);
	new_h = min(new_h, (unsigned)max_tex_size);
	//ELOG("Surface %dw %dh  %dnw %dnh  %dt  %dsw %dsh", w, h, new_w, new_h, (unsigned)max_tex_size, shift_w, shift_h);

	if ((new_w != (unsigned)surface->w) || (new_h != (unsigned)surface->h)) {
		/* Blit the image to a new surface that is a power of 2, and set the appropriate texture coords */
		SDL_Surface* o = surface;
		SDL_PixelFormat* f = surface->format;
		SDL_Rect src_rect = {
			0, 0,
			new_w, new_h
		};

		if ((unsigned)surface->w > new_w) {
			src_rect.x = (surface->w - new_w) / 2;
		}
		if ((unsigned)surface->h > new_h) {
			src_rect.y = (surface->h - new_h) / 2;
		}

		surface = SDL_CreateRGBSurface(o->flags, min(new_w, (unsigned)max_tex_size), min(new_h, (unsigned)max_tex_size), f->BitsPerPixel, f->Rmask, f->Gmask, f->Bmask, f->Amask);
		SDL_BlitSurface(o, &src_rect, surface, NULL);
		aspect = (float)o->w / o->h;

		/* I'm pretty sure these calculations are right */
		//ELOG("Aspect %g, %g", aspect, ASPECT);
		if ((unsigned)o->h >= max_tex_size || (unsigned)o->w >= max_tex_size) {
			//ELOG("%s", "Height too big");
			tmaxW = 1.0;
			tmaxH = 1.0/ASPECT;
		} else {
			if (aspect <= ASPECT) {
				//ELOG("%s", "Low aspect");
				tmaxW = (float)o->w / new_w;
				/* NB: Compiler should optimise (1.0/ASPECT) to a constant */
				if (shift_w < shift_h) {
					tmaxH = tmaxW * (1.0/ASPECT) / (1 << (shift_h - shift_w));
				} else {
					tmaxH = tmaxW * (1.0/ASPECT) * (1 << (shift_w - shift_h));
				}
			} else {
				//ELOG("%s", "High aspect");
				tmaxH = (float)o->h / new_h;
				if (shift_w < shift_h) {
					tmaxW = tmaxH * (ASPECT) * (1 << (shift_h - shift_w));
				} else {
					tmaxW = tmaxH * (ASPECT) / (1 << (shift_w - shift_h));
				}
			}
		}

		SDL_FreeSurface(o);
	}
	nOfColors = surface->format->BytesPerPixel;
	if (nOfColors == 4) {    // contains an alpha channel
		if (surface->format->Rmask == 0x000000ff) {
			texture_format = GL_RGBA;
		} else {
#ifdef GL_BGRA
			texture_format = GL_BGRA;
#else
			ELOG("%s", "warning: surface is not RGBA");
#endif
		}
	} else if (nOfColors == 3) {    // no alpha channel
		if (surface->format->Rmask == 0x000000ff) {
			texture_format = GL_RGB;
		} else {
#ifdef GL_BGR
			texture_format = GL_BGR;
#else
			ELOG("%s", "warning: surface is not RGB");
#endif
		}
	} else {
			ELOG("%s", "warning: the image is not truecolor..  this will probably break");
	}

	if (!has_tex) {
		has_tex = TRUE;
		glGenTextures(1, &tex);
		GL_CHECK_ERROR();
	}

	bind();
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );

	glTexImage2D(GL_TEXTURE_2D, 0, texture_format, surface->w, surface->h, 0, texture_format, GL_UNSIGNED_BYTE, surface->pixels);
	GL_CHECK_ERROR();

	return surface;
}

/* This is needed for subclasses */
bool Texture::bind() {
	if (has_tex) {
		glBindTexture(GL_TEXTURE_2D, tex);
		GL_CHECK_ERROR();
	} else {
		return FALSE;
	}
	return TRUE;
}

/* class Image */
void Image::_init() {
	configTex();
	scale(texW, texH);
	position(0, 0);
}

Image::Image() : Texture() {
}

Image::Image(const char* fname) : Texture(fname) {
	_init();
}

Image::Image(SDL_Surface* surface) : Texture(surface) {
	_init();
}

void Image::configTex() {
	if (has_tex) {
		SET_PAIR(tex_rect[3], 0, 0);
		SET_PAIR(tex_rect[2], tmaxW, 0);
		SET_PAIR(tex_rect[1], tmaxW, tmaxH);
		SET_PAIR(tex_rect[0], 0, tmaxH);
		//ELOG("(%f,%f) (%f,%f)", tex_rect[0][0], tex_rect[0][1], tex_rect[2][0], tex_rect[2][1]);

		/*corner[0][0] = corner[3][0] = corner[0][1] = corner[1][1] = 0;
		imgW = corner[1][0] = corner[2][0] = texW;
		imgH = corner[2][1] = corner[3][1] = texH;*/

		//scale(texW, texH);
	}
}

SDL_Surface* Image::fromSDLSurface(SDL_Surface* surface) {
	if (!(surface = Texture::fromSDLSurface(surface))) {
		return NULL;
	}
	configTex();
	return surface;
}

bool Image::render() {
	if (bind()) {
		glMatrixMode(GL_MODELVIEW);
		glPushMatrix();
		/* The scissor rect is from bottom-left pixel, so we need some fancy calculations */
		glScissor(WAX - imgW / 2 + imgX, HAX - imgH / 2 + imgY, imgW, imgH);
 
		glTranslatef(-imgW / 2 + imgX, -imgH / 2 + imgY, 0);

		glVertexPointer(2, GL_FLOAT, 0, corner); GL_CHECK_ERROR();
		glTexCoordPointer(2, GL_FLOAT, 0, tex_rect); GL_CHECK_ERROR();
		glDrawArrays(GL_TRIANGLE_FAN, 0, 4); GL_CHECK_ERROR();
		glPopMatrix();
		GL_CHECK_ERROR();
	} else {
		return FALSE;
	}
	return TRUE;
}

bool Image::position(GLfloat x, GLfloat y) {
	if (has_tex) {
		imgX = x;
		imgY = y;
		return TRUE;
	} else {
		return FALSE;
	}
}

bool Image::scale(GLfloat nw, GLfloat nh) {
	if (has_tex) {
		corner[1][0] = corner[2][0] = corner[0][0] + nw;
		corner[2][1] = corner[3][1] = corner[0][1] + nh;
		imgW = nw;
		imgH = nh;
		return TRUE;
	} else {
		return FALSE;
	}
}

/* class Scrolling */
void Scrolling::_init() {
	c_scrollX = c_scrollY = scrollX = scrollY = 0;
}

Scrolling::Scrolling()
	: Image() {
	_init();
}

Scrolling::Scrolling (const char* fname)
	: Image(fname) {
	_init();
}

Scrolling::Scrolling (SDL_Surface* surface)
	: Image(surface) {
	_init();
}

void Scrolling::renderRow() {
	Image::render();
	if (c_scrollX < 0.0) {
		glTranslatef(imgW, 0, 0);
		Image::render();
	} else if (c_scrollX > 0.0) {
		glTranslatef(-imgW, 0, 0);
		Image::render();
 	}
}

bool Scrolling::render() {
	if (has_tex) {
		//ELOG("(%f,%f) (%f,%f)", corner[0][0], corner[0][1], corner[2][0], corner[2][1]);
		glMatrixMode(GL_MODELVIEW);
		/* There are 9 possible image positions to render:
		   A B C
		   D E F
		   G H I
		 * Up to 4 will be rendered in any iteration
		 * Position E will always be in view */
		glPushMatrix();
		c_scrollX = fmod(c_scrollX + scrollX, imgW);
		c_scrollY = fmod(c_scrollY + scrollY, imgH);

		glTranslatef(c_scrollX, c_scrollY, 0);

		glPushMatrix();
		/* Positions D E and F */
		renderRow();
		// Reset the matrix for the other positions
		glPopMatrix();
		glPushMatrix();

		if (c_scrollY < 0.0) {
			/* Positions A B and C */
			glTranslatef(0, imgH, 0);
			renderRow();
		} else if (c_scrollY > 0.0) {
			/* Positions G H and I */
			glTranslatef(0, -imgH, 0);
			renderRow();
		}
		glPopMatrix();

		glPopMatrix();
		GL_CHECK_ERROR();
		return TRUE;
	} else {
		return FALSE;
	}
}

/* vim:set ts=2 sts=2 sw=2 noexpandtab: */
